package edu.northwestern.cbits.purple_robot_manager.util; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.Context; import edu.northwestern.cbits.purple_robot_manager.logging.LogManager; public class DBSCAN { public static final int POPULATION = 10; public static final double DISTANCE = 0.001; private double _minDistance = 0; private int _minPopulation = 0; private HashSet<Point> _points = new HashSet<>(); public static class Point { private double _x = 0; private double _y = 0; private Cluster _cluster = null; public Point(double x, double y) { this._x = x; this._y = y; } public double x() { return this._x; } public double y() { return this._y; } public int hashCode() { return (this._x + "." + this._y).hashCode(); } public boolean equals(Object o) { if (o instanceof Point) { Point p = (Point) o; return (p._x == this._x) && (p._y == this._y); } return false; } public void setCluster(Cluster cluster) { this._cluster = cluster; } public double distanceFrom(Point p) { double xDelta = p._x - this._x; double yDelta = p._y - this._y; return Math.sqrt((xDelta * xDelta) + (yDelta * yDelta)); } public Cluster getCluster() { return this._cluster; } public Cluster cluster(Point other) { Cluster cluster = null; if (this._cluster == null && other._cluster == null) { cluster = new Cluster(); cluster.addPoint(this); cluster.addPoint(other); } else if (this._cluster != null && other._cluster != null) { cluster = this._cluster; if (this._cluster != other._cluster) this._cluster.assimilate(other._cluster); } else if (this._cluster != null) { cluster = this._cluster; cluster.addPoint(other); } else if (other._cluster != null) { cluster = other._cluster; cluster.addPoint(this); } return cluster; } } public static class Cluster { private HashSet<Point> _points = new HashSet<>(); private String _name = null; public void addPoint(Point p) { if (p.getCluster() != null) p.getCluster().removePoint(p); this._points.add(p); p.setCluster(this); } private void removePoint(Point p) { this._points.remove(p); } public int population() { return this._points.size(); } public void assimilate(Cluster c) { ArrayList<Point> points = new ArrayList<>(); points.addAll(c.getPoints()); for (Point p : points) { this.addPoint(p); } } public Collection<Point> getPoints() { return this._points; } public String getName() { return this._name; } public void setName(String name) { this._name = name; } public JSONObject getJSON() throws JSONException { if (this._name != null) { JSONObject json = new JSONObject(); json.put("name", this._name); JSONArray points = new JSONArray(); for (Point point : this._points) { JSONArray pointArray = new JSONArray(); pointArray.put(point._x); pointArray.put(point._y); points.put(pointArray); } json.put("points", points); return json; } return null; } public Cluster() { super(); } public Cluster(JSONObject json) throws JSONException { super(); if (json.has("name")) this._name = json.getString("name"); JSONArray points = json.getJSONArray("points"); for (int i = 0; i < points.length(); i++) { JSONArray point = points.getJSONArray(i); Point p = new Point(point.getDouble(0), point.getDouble(1)); this.addPoint(p); } } public Collection<Point> getPoints(int count) { if (this._points.size() <= count) return this._points; ArrayList<Point> shuffled = new ArrayList<>(); shuffled.addAll(this._points); Collections.shuffle(shuffled, new SecureRandom()); return shuffled.subList(0, count); } } public DBSCAN(Context context, double distance, int population) { this._minDistance = distance; this._minPopulation = population; Collection<Cluster> clusters = DBSCAN.fetchClusters(context); for (Cluster c : clusters) { for (Point p : c.getPoints()) this._points.add(p); } } public void addPoint(Point p) { this._points.add(p); } public Collection<Cluster> calculate(Context context) { HashSet<Cluster> clusters = new HashSet<>(); Point[] xPoints = this._points.toArray(new Point[0]); Point[] yPoints = this._points.toArray(new Point[0]); Arrays.sort(xPoints, new Comparator<Point>() { public int compare(Point one, Point two) { if (one._x > two._x) return 1; else if (one._x < two._x) return -1; return 0; } }); for (int i = 0; i < xPoints.length; i++) { Point here = xPoints[i]; Point back = null; Point next = null; if (i > 0) back = xPoints[i - 1]; if (i < xPoints.length - 1) next = xPoints[i + 1]; if (back != null && Math.abs(here._x - back._x) < this._minDistance) { if (here.distanceFrom(back) < this._minDistance) clusters.add(here.cluster(back)); } if (next != null && Math.abs(here._x - next._x) < this._minDistance) { if (here.distanceFrom(next) < this._minDistance) clusters.add(here.cluster(next)); } } Arrays.sort(yPoints, new Comparator<Point>() { public int compare(Point one, Point two) { if (one._y > two._y) return 1; else if (one._y < two._y) return -1; return 0; } }); for (int i = 0; i < yPoints.length; i++) { Point here = yPoints[i]; Point back = null; Point next = null; if (i > 0) back = yPoints[i - 1]; if (i < yPoints.length - 1) next = yPoints[i + 1]; if (back != null && Math.abs(here._y - back._y) < this._minDistance) { if (here.distanceFrom(back) < this._minDistance) clusters.add(here.cluster(back)); } if (next != null && Math.abs(here._y - next._y) < this._minDistance) { if (here.distanceFrom(next) < this._minDistance) clusters.add(here.cluster(next)); } } HashSet<Cluster> toReturn = new HashSet<>(); for (Cluster c : clusters) { if (c.population() >= this._minPopulation) toReturn.add(c); } return toReturn; } private static File getClusterFile(Context context) { File dataDir = context.getFilesDir(); File clusterDir = new File(dataDir, "Cluster Data"); if (clusterDir.exists() == false) clusterDir.mkdirs(); File clusterFile = new File(clusterDir, "cluster.json"); return clusterFile; } public static void persistClusters(Context context, ArrayList<Cluster> clusters, double distance, double population) { File clusterFile = DBSCAN.getClusterFile(context); try { JSONObject toWrite = new JSONObject(); toWrite.put("distance", distance); toWrite.put("population", population); JSONArray clusterList = new JSONArray(); for (Cluster cluster : clusters) { JSONObject clusterJSON = cluster.getJSON(); if (clusterJSON != null) clusterList.put(cluster.getJSON()); } toWrite.put("clusters", clusterList); FileOutputStream fout = new FileOutputStream(clusterFile); fout.write(toWrite.toString().getBytes(Charset.defaultCharset().name())); fout.flush(); fout.close(); } catch (JSONException e) { LogManager.getInstance(context).logException(e); } catch (IOException e) { LogManager.getInstance(context).logException(e); } } public static String inCluster(Context context, double latitude, double longitude) { Point point = new Point(latitude, longitude); Collection<Cluster> clusters = DBSCAN.fetchClusters(context); for (Cluster cluster : clusters) { if (cluster.getName() != null) { for (Point p : cluster.getPoints()) { if (point.distanceFrom(p) <= DBSCAN.DISTANCE) return cluster.getName(); } } } return null; } private static Collection<Cluster> fetchClusters(Context context) { HashSet<Cluster> clusters = new HashSet<>(); File clusterFile = DBSCAN.getClusterFile(context); if (clusterFile.exists()) { try { FileInputStream fin = new FileInputStream(clusterFile); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int read = 0; while ((read = fin.read(buffer, 0, buffer.length)) != -1) { baos.write(buffer, 0, read); } fin.close(); String fileString = new String(baos.toByteArray(), Charset.defaultCharset().name()); JSONObject clusterJson = new JSONObject(fileString); JSONArray clusterList = clusterJson.getJSONArray("clusters"); for (int i = 0; i < clusterList.length(); i++) { clusters.add(new Cluster(clusterList.getJSONObject(i))); } } catch (JSONException | IOException e) { LogManager.getInstance(context).logException(e); } } return clusters; } }